/**
 * 定点数 Vector2
 * 
 * @description
 * 使用定点数实现的 2D 向量，确保跨平台计算一致性
 */

import { FixedPoint } from './FixedPoint';
import { Vector2 } from '../rvo/Common';

/**
 * 定点数 Vector2 类
 */
export class FixedVector2 {
    
    public x: FixedPoint;
    public y: FixedPoint;
    
    constructor(x: FixedPoint | number = 0, y: FixedPoint | number = 0) {
        if (typeof x === 'number') {
            this.x = FixedPoint.fromNumber(x);
            this.y = FixedPoint.fromNumber(y as number);
        } else {
            this.x = x;
            this.y = y as FixedPoint;
        }
    }
    
    static fromVector2(v: Vector2): FixedVector2 {
        return new FixedVector2(v.x, v.y);
    }
    
    toVector2(): Vector2 {
        return new Vector2(this.x.toNumber(), this.y.toNumber());
    }
    
    plus(other: FixedVector2): FixedVector2 {
        return new FixedVector2(this.x.add(other.x), this.y.add(other.y));
    }
    
    plusInPlace(other: FixedVector2): this {
        this.x = this.x.add(other.x);
        this.y = this.y.add(other.y);
        return this;
    }
    
    minus(other: FixedVector2): FixedVector2 {
        return new FixedVector2(this.x.sub(other.x), this.y.sub(other.y));
    }
    
    minusInPlace(other: FixedVector2): this {
        this.x = this.x.sub(other.x);
        this.y = this.y.sub(other.y);
        return this;
    }
    
    multiply(other: FixedVector2): FixedPoint {
        return this.x.mul(other.x).add(this.y.mul(other.y));
    }
    
    scale(factor: FixedPoint | number): FixedVector2 {
        const f = typeof factor === 'number' ? FixedPoint.fromNumber(factor) : factor;
        return new FixedVector2(this.x.mul(f), this.y.mul(f));
    }
    
    scaleInPlace(factor: FixedPoint | number): this {
        const f = typeof factor === 'number' ? FixedPoint.fromNumber(factor) : factor;
        this.x = this.x.mul(f);
        this.y = this.y.mul(f);
        return this;
    }
    
    copy(v: FixedVector2 | { x: number; y: number }): this {
        if (v instanceof FixedVector2) {
            this.x = v.x.clone();
            this.y = v.y.clone();
        } else {
            this.x = FixedPoint.fromNumber(v.x);
            this.y = FixedPoint.fromNumber(v.y);
        }
        return this;
    }
    
    clone(): FixedVector2 {
        return new FixedVector2(this.x.clone(), this.y.clone());
    }
    
    lengthSqr(): FixedPoint {
        return this.x.square().add(this.y.square());
    }
    
    length(): FixedPoint {
        return this.lengthSqr().sqrt();
    }
    
    normalize(): FixedVector2 {
        const len = this.length();
        if (len.raw === 0) return new FixedVector2(0, 0);
        return new FixedVector2(this.x.div(len), this.y.div(len));
    }
    
    normalizeInPlace(): this {
        const len = this.length();
        if (len.raw > 0) {
            this.x = this.x.div(len);
            this.y = this.y.div(len);
        }
        return this;
    }
    
    set(x: FixedPoint | number, y: FixedPoint | number): this {
        if (typeof x === 'number') {
            this.x = FixedPoint.fromNumber(x);
            this.y = FixedPoint.fromNumber(y as number);
        } else {
            this.x = x;
            this.y = y as FixedPoint;
        }
        return this;
    }
    
    reset(): this {
        this.x = FixedPoint.ZERO.clone();
        this.y = FixedPoint.ZERO.clone();
        return this;
    }
    
    toString(): string {
        return `FixedVector2(${this.x.toString()}, ${this.y.toString()})`;
    }
    
    static dot(v1: FixedVector2, v2: FixedVector2): FixedPoint {
        return v1.multiply(v2);
    }
    
    static det(v1: FixedVector2, v2: FixedVector2): FixedPoint {
        return v1.x.mul(v2.y).sub(v1.y.mul(v2.x));
    }
}

/**
 * 定点数 RVOMath
 */
export class FixedRVOMath {
    
    static readonly RVO_EPSILON = FixedPoint.EPSILON;
    
    static absSq(v: FixedVector2): FixedPoint {
        return v.lengthSqr();
    }
    
    static abs(v: FixedVector2): FixedPoint {
        return v.length();
    }
    
    static normalize(v: FixedVector2): FixedVector2 {
        return v.normalize();
    }
    
    static det(v1: FixedVector2, v2: FixedVector2): FixedPoint {
        return FixedVector2.det(v1, v2);
    }
    
    static sqr(x: FixedPoint): FixedPoint {
        return x.square();
    }
    
    static distSqPointLineSegment(
        v1: FixedVector2,
        v2: FixedVector2,
        v3: FixedVector2
    ): FixedPoint {
        const aux1 = v3.minus(v1);
        const aux2 = v2.minus(v1);
        const aux2LenSq = aux2.lengthSqr();
        
        if (aux2LenSq.raw === 0) return aux1.lengthSqr();
        
        const r = aux1.multiply(aux2).div(aux2LenSq);
        
        if (r.lt(FixedPoint.ZERO)) {
            return aux1.lengthSqr();
        } else if (r.gt(FixedPoint.ONE)) {
            return v3.minus(v2).lengthSqr();
        } else {
            const proj = v1.plus(aux2.scale(r));
            return v3.minus(proj).lengthSqr();
        }
    }
    
    static leftOf(a: FixedVector2, b: FixedVector2, c: FixedVector2): FixedPoint {
        return FixedRVOMath.det(a.minus(c), b.minus(a));
    }
}

